package highlighter.visitor.impl;

import java.awt.Color;

import javax.swing.JFrame;
import javax.swing.JScrollPane;

import highlighter.app.ColorPane;
import highlighter.ast.impl.*;
import highlighter.parser.sym;
import highlighter.visitor.IASTVisitor;

/**
 * Pretty print visitor. It will print AST at console.
 */
public class PrettyPrintVisitor implements IASTVisitor {

	private ColorPane pane;
	
	public PrettyPrintVisitor() {
		
		pane = new ColorPane();
		
		JFrame f = new JFrame("ColorPane example");
	    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	    f.setContentPane(new JScrollPane(pane));
	    f.setSize(800, 600);
	    f.setVisible(true);
	}
	
	// depth in AST used to indent node name
	private int depth = 0;

	private void addSpaces(ColorPane pane, int depth) {
		for(int i=0; i<depth; i++)
			pane.append(Color.blue, "    ");
	}
	
	@Override
	public Object visit(Program p) {
		for (NamespaceDef nd : p.getNamespaces()) {
			nd.accept(this);
		}
		return null;
	}

	@Override
	public Object visit(DeclareStmt s) {
		depth++;
		pane.append(new Color(0,0,0), "\n");
		addSpaces(pane, depth);
			
		if (s.getModifier().toString().length()>0)
			pane.append(new Color(0,150,0), s.getModifier().toString()+" ");
		if (s.getIsStatic())
			pane.append(new Color(0,150,0), "static ");
		if (s.getIsFinal())
			pane.append(new Color(0,150,0), "final ");
		pane.append(new Color(0, 0, 150), s.getVarType().toString()+" ");

		s.getId().accept(this);
		pane.append(Color.black, ";");
		depth--;

		return null;
	}

	@Override
	public Object visit(AssignStmt s) {
		depth++;
		pane.append(Color.red, "\n");
		addSpaces(pane, depth);
			
		s.getId().accept(this);
		pane.append(Color.black, " = ");
		s.getExpr().accept(this);
		pane.append(Color.black, ";");
		depth--;

		return null;
	}

	@Override
	public Object visit(IfStmt s) {
		depth++;
		pane.append(Color.blue, "\n");
		addSpaces(pane, depth);
			
		pane.append(Color.blue, "if");
		s.getRelExpr().accept(this);
		s.getThenBody().accept(this);
		if (s.getElseBody() != null) {
			pane.append(Color.blue, "\n");
			addSpaces(pane, depth);
				
			pane.append(Color.blue, "else ");
			s.getElseBody().accept(this);
		}

		depth--;

		return null;
	}

	@Override
	public Object visit(WhileStmt s) {
		depth++;
		pane.append(Color.blue, "\n");
		addSpaces(pane, depth);
			
		pane.append(Color.blue, "while");
		s.getRelExpr().accept(this);
		s.getBody().accept(this);

		depth--;

		return null;
	}

	@Override
	public Object visit(Body body) {
		//depth++;
		pane.append(Color.black, "\n");
		addSpaces(pane, depth);
			
		pane.append(Color.black, "{");
		for (Statement statement : body.getStmts()) {
			statement.accept(this);
		}
		pane.append(Color.black, "\n");
		addSpaces(pane, depth);
			
		pane.append(Color.black, "}");
		//depth--;

		return null;
	}

	@Override
	public Object visit(ArithmeticExpr aexp) {
		depth++;

		if (aexp.getLhs() != null) {
			aexp.getLhs().accept(this);
			depth++;
			pane.append(Color.black, (aexp.getOperation() == sym.PLUS) ? " + " : " - ");
			depth--;
		}
		aexp.getFactor().accept(this);

		depth--;

		return null;
	}

	@Override
	public Object visit(RelationalExpr rexp) {
		depth++;
		pane.append(Color.black, " ( ");

		rexp.getLhs().accept(this);
		depth++;
		
		switch(rexp.getRelOp())
		{
			case sym.LT:
			{
				pane.append(Color.black, " < ");
				break;
			}
			case sym.GT:
			{
				pane.append(Color.black, " > ");
				break;
			}
			case sym.DEQ:
			{
				pane.append(Color.black, " == ");
				break;
			}
			case sym.NEQ:
			{
				pane.append(Color.black, " != ");
				break;
			}
		}
			
		depth--;
		rexp.getRhs().accept(this);
		pane.append(Color.black, " ) ");
		depth--;

		return null;
	}

	@Override
	public Object visit(Factor factor) {
		depth++;

		if (factor.getFactor() != null) {
			factor.getFactor().accept(this);
			depth++;
			pane.append(Color.black, factor.getOperation() == sym.TIMES ? " * " : " / ");
			depth--;
		}
		factor.getTerm().accept(this);

		depth--;

		return null;
	}

	@Override
	public Object visit(TermExpr term) {
		depth++;

		term.getExpr().accept(this);

		depth--;

		return null;
	}

	@Override
	public Object visit(TermNumber term) {
		depth++;

		term.getNumber().accept(this);

		depth--;

		return null;
	}

	@Override
	public Object visit(TermId term) {
		depth++;

		term.getId().accept(this);

		depth--;

		return null;
	}

	@Override
	public Object visit(IntConst intConst) {
		depth++;
		pane.append(Color.red, String.valueOf(intConst.getValue()));
		depth--;
		return null;
	}

	@Override
	public Object visit(Identifier identifier) {
		depth++;
		pane.append(Color.red, identifier.getName());
		depth--;
		return null;
	}
	
	@Override
	public Object visit(Method method) {
		depth++;
		pane.append(new Color(0,0,0), "\n");
		addSpaces(pane, depth);
			
		if (method.getModifier().toString().length()>0)
			pane.append(new Color(0,150,0), method.getModifier().toString()+" ");
		if (method.getIsStatic())
			pane.append(new Color(0,150,0), "static ");
		if (method.getIsFinal())
			pane.append(new Color(0,150,0), "final ");
		pane.append(new Color(0, 0, 150), method.getVarType().toString()+" ");
		method.getId().accept(this);
		pane.append(Color.black, "(");
		method.getParams().accept(this);
		pane.append(Color.black, ")");
		method.getBody().accept(this);
		depth--;
		return null;
	}
	
	@Override
	public Object visit(ParamList params) {
		if (params!=null)
			for (Parameter param : params.getParamList())
			{
				param.accept(this);
				if (params.getParamList().lastIndexOf(param) < params.getParamList().size()-1)
					pane.append(Color.black, ",");
			}
		return null;
	}
	
	@Override
	public Object visit(Parameter param) {
		depth++;
		pane.append(new Color(0, 0, 150), param.getVarType().toString()+" ");
		param.getId().accept(this);
		depth--;

		return null;
	}
	
	@Override
	public Object visit(ClassDef classDef) {
		depth++;
		pane.append(new Color(0,0,0), "\n");
		addSpaces(pane, depth);
			
		if (classDef.getModif().toString().length()>0)
			pane.append(new Color(0,150,0), classDef.getModif().toString()+" ");
		if (classDef.isStatic())
			pane.append(new Color(0,150,0), "static ");
		pane.append(new Color(150,0,150), "class ");
		classDef.getId().accept(this);
		pane.append(Color.black,"\n");
		addSpaces(pane, depth);
			
		pane.append(Color.black,"{");
		classDef.getClassBody().accept(this);
		pane.append(Color.black,"\n");
		addSpaces(pane, depth);
			
		pane.append(Color.black,"}");
		depth--;
		return null;
	}
	/**
	 * Pretty prints a node label.
	 * 
	 * @param label
	 *            the label
	 */
	private String prettyPrint(String label) {
		
		String str = "";
		
		for (int i = 0; i < depth; i++) {
			str = str + " ";
		}

		return str + label + "\n";
	}

	@Override
	public Object visit(ClassBody classBody) {
		for (Statement statement : classBody.getStmts()) {
			statement.accept(this);
		}
		return null;
	}

	@Override
	public Object visit(NamespaceDef namespaceDef) {

		pane.append(new Color(0,0,150), "\nnamespace ");
		namespaceDef.getId().accept(this);
		pane.append(Color.black,"\n");
		pane.append(Color.black,"{");
		namespaceDef.getClassBody().accept(this);
		pane.append(Color.black,"\n");		
		pane.append(Color.black,"}");

		return null;
	}

	@Override
	public Object visit(NamespaceBody namespaceBody) {
		for (ClassDef cd : namespaceBody.getClassDefs()) {
			cd.accept(this);
		}
		return null;
	}

	@Override
	public Object visit(ReturnStmt returnStmt) {
		depth++;
		pane.append(Color.red, "\n");
		addSpaces(pane, depth);
		pane.append(new Color(0,150,0), "return ");	
		returnStmt.getId().accept(this);
		pane.append(Color.black, ";");
		depth--;
		return null;
	}

}
